
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/kernel.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/services/bas.h>
#include <zephyr/bluetooth/services/hrs.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_init, CONFIG_BT_LOG_LEVEL);

#if defined(CONFIG_GPIO)
#include <zephyr/drivers/gpio.h>

/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS   1000

/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)

/*
 * A build error on this line means your board is unsupported.
 * See the sample documentation for information on how to fix this.
 */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
#endif

static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
	BT_DATA_BYTES(BT_DATA_UUID16_ALL,
				BT_UUID_16_ENCODE(BT_UUID_HRS_VAL),
				BT_UUID_16_ENCODE(BT_UUID_BAS_VAL),
				BT_UUID_16_ENCODE(BT_UUID_DIS_VAL),
				BT_UUID_16_ENCODE(BT_UUID_DIS_SOFTWARE_REVISION_VAL),
				BT_UUID_16_ENCODE(BT_UUID_DIS_HARDWARE_REVISION_VAL),
				BT_UUID_16_ENCODE(BT_UUID_DIS_SYSTEM_ID_VAL),
				BT_UUID_16_ENCODE(BT_UUID_DIS_SERIAL_NUMBER_VAL),
				BT_UUID_16_ENCODE(BT_UUID_DIS_FIRMWARE_REVISION_VAL),
				BT_UUID_16_ENCODE(BT_UUID_DIS_HARDWARE_REVISION_VAL),
				BT_UUID_16_ENCODE(BT_UUID_DIS_PNP_ID_VAL))
};

/**
 * @description:
 * @param {bt_conn} *conn
 * @param {uint8_t} err
 * @return {*}
 */
static void connected(struct bt_conn *conn, uint8_t err)
{
	if (err) {
		LOG_INF("Connection failed (err 0x%02x)\n", err);
	} else {
		LOG_INF("Connected\n");
	}
}

/**
 * @description:
 * @param {bt_conn} *conn
 * @param {uint8_t} reason
 * @return {*}
 */
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	LOG_INF("Disconnected (reason 0x%02x)\n", reason);
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
	.connected = connected,
	.disconnected = disconnected,
};

/**
 * @description: 
 * @return {*}
 */
static void bt_ready(void)
{
	int err;

	LOG_INF("Bluetooth initialized\n");

	err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
	if (err) {
		LOG_INF("Advertising failed to start (err %d)\n", err);
		return;
	}

	LOG_INF("Advertising successfully started\n");
}

/**
 * @description:
 * @param {bt_conn} *conn
 * @return {*}
 */
static void auth_cancel(struct bt_conn *conn)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	LOG_INF("Pairing cancelled: %s\n", addr);
}

static struct bt_conn_auth_cb auth_cb_display = {
	.cancel = auth_cancel,
};

/**
 * @description:
 * @return {*}
 */
static void bas_notify(void)
{
	uint8_t battery_level = bt_bas_get_battery_level();

	battery_level--;

	if (!battery_level) {
		battery_level = 100U;
	}

	bt_bas_set_battery_level(battery_level);
}

/**
 * @description:
 * @return {*}
 */
static void hrs_notify(void)
{
	static uint8_t heartrate = 90U;

	/* Heartrate measurements simulation */
	heartrate++;
	if (heartrate == 160U) {
		heartrate = 90U;
	}

	bt_hrs_notify(heartrate);
}

static K_KERNEL_STACK_DEFINE(bt_init_stack, 1024);
static struct k_thread bt_init_thread_data;
static void thread_bt_entry(void);

/**
 * @description:
 * @return {*}
 */
void bt_init(void)
{
	const k_tid_t tid =
	k_thread_create(&bt_init_thread_data, bt_init_stack,
			K_KERNEL_STACK_SIZEOF(bt_init_stack),
			(k_thread_entry_t)thread_bt_entry, NULL, NULL, NULL,
			K_PRIO_COOP(6),
			0, K_NO_WAIT);
	k_thread_name_set(tid, "bt_init");
}

/**
 * @description:
 * @return {*}
 */
void thread_bt_entry(void)
{
	int err;

	err = bt_enable(NULL);
	if (err) {
		LOG_INF("Bluetooth init failed (err %d)\n", err);
		return;
	}

	bt_ready();

	bt_conn_auth_cb_register(&auth_cb_display);

#if defined(CONFIG_GPIO)
    int ret;

	if (!device_is_ready(led.port)) {
		return;
	}

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return;
	}
#endif

    LOG_INF("%s starts to run..\n", __func__);

	/* Implement notification. At the moment there is no suitable way
	 * of starting delayed work so we do it here
	 */
	while (1) {
		k_sleep(K_SECONDS(1));

		/* Heartrate measurements simulation */
		hrs_notify();

		/* Battery level simulation */
		bas_notify();

#if defined(CONFIG_GPIO)
        ret = gpio_pin_toggle_dt(&led);
		if (ret < 0) {
			return;
		}
#endif
	}
}
